#pragma once
// oj_control 模块：将 oj_model 和 oj_view 相结合，作为一个控制器，oj_model 相当于对数据的处理，oj_view 相当于根据数据构建前端网页，展示给用户

#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "../common/httplib.h"

#include "../common/log.hpp"
#include "../common/util.hpp"
#include "oj_model.hpp"
#include "oj_view.hpp"

namespace ns_control
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 描述提供后台judge服务的机器
    class Machine
    {
    public:
        Machine() 
            :_ip("127.0.0.1")
            ,_port(0)
            ,_load(0)
            ,_mutex(nullptr)
        {}
        ~Machine() {}
    
        std::string Ip() { return _ip; }

        uint16_t Port() { return _port; }

        // 保证拿到这个主机负载均衡情况时，是没有正在进行 _load 加减的
        uint64_t Load()
        {
            uint64_t load;
            if(_mutex) _mutex->lock();
            load = _load;
            if(_mutex) _mutex->unlock();
            return load;
        }

        void SetLoad(const uint64_t load)
        {
            if(_mutex) _mutex->lock();
            _load = load;
            if(_mutex) _mutex->unlock();    
        }

        // 增加 负载情况
        void IncLoad()
        {
            if(_mutex) _mutex->lock();
            ++_load;
            if(_mutex) _mutex->unlock();    
        }

        // 减少 负载情况
        void DecLoad()
        {
            if(_mutex) _mutex->lock();
            --_load;
            if(_mutex) _mutex->unlock();    
        }

    public:
        std::string _ip;     // 主机的ip地址
        uint16_t _port;      // 主机提供服务的端口号
        std::mutex* _mutex;  // c++提供的mutex是防止拷贝的，所以使用指针拷贝
    private:
        uint64_t _load;      // 该主机的负载情况
    };

    const std::string service_path = "./conf/service_machine.conf";

    // 负载均衡模块 
    class LoadBalance
    {
    public:
        LoadBalance() 
        {
            assert(LoadConf(service_path));
            LOG(INFO) << "加载所有judge服务主机：service_machine.conf 成功！" << std::endl;
        }
        ~LoadBalance() {}

        // 加载所有的主机，根据所有conf的路径
        bool LoadConf(const std::string& service_machine)
        {
            std::ifstream in(service_machine);
            if(!in.is_open()) 
            {
                LOG(WARNING) << "加载 service_machine.conf 文件失败！" << std::endl;
                return false;
            }
            std::string line;
            while(std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                // tokens[0] 代表 ip, tokens[1] 代表 port
                Machine machine;
                machine._ip = tokens[0];
                machine._port = stoi(tokens[1]);
                machine._mutex = new std::mutex();
                online.push_back(machines.size());
                machines.push_back(machine);
            }
            in.close();
            return true;
        }

        // 选择一台负载最低的主机进行 judge 服务，从 online 在线的主机中选择
        // id：输出型参数，代表着最后选择的主机的编号
        // m：输出型参数，代表着最后选择的主机的地址
        bool SmartChoice(int* id, Machine** m)
        {
            // 这里选择的策略是：轮询+hash，选择负载最少的主机
            // 我们可能需要离线该主机
            _mutex.lock();
            int n = online.size();
            if(n == 0)
            {
                LOG(FATAL) << "没有能够提供服务的主机！请找运维的同事是否是主机问题！" << std::endl;
                _mutex.unlock();
                return false;
            }
            // 轮询遍历负载最少的主机
            *id = online[0];
            *m = &machines[online[0]];
            uint64_t mi = machines[online[0]].Load();
            for(int i = 1;i < n;i++)
            {
                uint64_t cur = machines[online[i]].Load();
                if(cur < mi)
                {
                    // 更新
                    *id = online[i];
                    *m = &machines[online[i]];
                    mi = cur;
                }
            }
            _mutex.unlock();
            return true;
        }

        // 上线一些主机
        void OnlineMachine()
        {
            // 约定：当所有主机都下线的时候，统一上线主机
            _mutex.lock();
            online.insert(online.begin(),offline.begin(),offline.end());
            offline.clear();
            _mutex.unlock();
            LOG(INFO) << "所有主机均已经重新上线！" << std::endl;
        }

        // 下线 id 主机
        void OfflineMachine(const int id)
        {
            _mutex.lock();
            auto it = find(online.begin(),online.end(),id);
            if(it == online.end())
            {
                _mutex.unlock();
                return;
            }
            machines[online[*it]].SetLoad(0);
            offline.push_back(online[*it]);
            online.erase(it);
            _mutex.unlock();
        }

    private:
        std::vector<Machine> machines;  // 所有能提供judge服务的主机，约定数组下标就是主机的编号
        std::vector<int> online;  // 所有在线的主机
        std::vector<int> offline;  // 所有离线的主机
        std::mutex _mutex;  // 保证负载均衡的线程安全
    };

    // 这是我们核心业务逻辑的控制器
    class Control
    {
    public:
        Control() {}
        ~Control() {}

        // 用户的各种请求：根据题目数据构建网页，使用 oj_view 构建
        // html: 输出型参数

        // 上线所有主机
        void RecoverAllHost()
        {
            _load_balance.OnlineMachine();
        }

        // 请求题目列表，返回题目列表的 html
        bool AllQuestions(std::string* html)
        {
            std::vector<Question> questions;
            if(_model.GetAllQuestions(&questions))
            {
                // 获取题目列表信息成功，将所有的题目数据构建成网页
                // 先将题目的编号顺序搞好
                std::sort(questions.begin(),questions.end(),[](const Question& q1, const Question& q2){
                    return stoi(q1.num) < stoi(q2.num);
                });
                _view.AllExpendHtml(questions, html);
            }
            else
            {
                *html = "获取所有题目列表失败！请检查...";
                return false;
            }
            return true;
        }

        // 请求一个题目，返回这个题目的 html
        bool OneQuestion(const std::string& num, std::string* html)
        {
            Question q;
            if(_model.GetOneQuestion(num, &q))
            {
                // 获取单个题目信息成功，将单个题目数据构建成网页
                _view.OneExpendHtml(q, html);
            }
            else
            {
                *html = "获取编号为" + num + "的题目列表失败！请检查...";
                return false;
            }
            return true;
        }

        void Judge(const std::string& num, const std::string& in_json, std::string* out_json)
        {
            // 1. 根据题目编号，获取对应的题目信息
            Question q;
            _model.GetOneQuestion(num, &q);
            // 2. in_json 进行反序列化，得到题目的 id，得到用户提交的源代码，还有input用户自己提交的测试样例信息（作为扩展）
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();
            std::string input = in_value["input"].asString();
            // 3. 重新拼接用户的代码，形成新的代码，用于后台进行判断代码正确性
            // 之前定义的 tail.cpp，直接拼接到用户的代码之中，通过判断测试用例的正确，来判断用户提交的代码是否正确
            Json::Value compile;
            compile["input"] = input;
            compile["code"] = code + q.tail;
            compile["cpu_limit"] = q.cpu_limit;
            compile["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile);
            // 4. 选择合适的主机，规则：一直寻找，直到没有主机可以用为止
            while(true)
            {
                int id = 0;
                Machine* m = nullptr;
                if(!_load_balance.SmartChoice(&id, &m)) break;
                LOG(INFO) << "选择主机成功！主机id：" << id << ",详情：" << m->Ip() << ":" << m->Port() << std::endl;
                // 5. 发送 http 请求编译运行代码，得到运行的结果
                Client cli(m->Ip(), m->Port());
                m->IncLoad();  // 负载+1
                auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8");
                if(res)
                {
                    if(res->status == 200)  // 状态码为 200 时才是请求成功的！
                    {
                        // 6. 将结果赋值给 out_json
                        *out_json = res->body;
                        m->DecLoad();
                        break;
                    }
                }                
                else
                {
                    LOG(ERROR) << "当前请求的主机id：" << id << ", 详情：" << m->Ip() << ":" << m->Port() << " 可能已经下线！" << std::endl;
                    // m->DecLoad();  // 可以没有必要，因为下线主机的时候会将负载清 0
                    // 这台主机失败了，肯定是离线了，下线该主机
                    _load_balance.OfflineMachine(id);
                }
            }
        }

    private:
        Model _model;  // 提供后台数据
        View _view;    // 提供 html 渲染
        LoadBalance _load_balance;  // 核心的负载均衡选择器
    };
}